#include "openssl/evp.h"

#include "imhash.h"

static inline EVP_MD_CTX* _getContext(void* pData)
{
	return (EVP_MD_CTX*)pData;
}

static inline const EVP_MD* _getMD(int type)
{
	switch(type)
	{
	case IMHash::HT_INVALID: return EVP_md_null();
	case IMHash::HT_MD5: return EVP_md5();
	case IMHash::HT_SHA: return EVP_sha();
	case IMHash::HT_SHA1: return EVP_sha1();
	case IMHash::HT_SHA224: return EVP_sha224();
	case IMHash::HT_SHA256: return EVP_sha256();
	case IMHash::HT_SHA384: return EVP_sha384();
	case IMHash::HT_SHA512: return EVP_sha512();
	case IMHash::HT_DSS: return EVP_dss();
	case IMHash::HT_DSS1: return EVP_dss1();
	//case IMHash::HT_MDC2: return EVP_mdc2();
	case IMHash::HT_RIPEMD160: return EVP_ripemd160();
	}

	return NULL;
}

IMHash::IMHash() : m_ctx(NULL)
{
}

IMHash::~IMHash()
{
	if ( m_ctx )
	{
		EVP_MD_CTX* ctx(_getContext(m_ctx));
		EVP_MD_CTX_cleanup(ctx);
		delete ctx;
		m_ctx = NULL;
	}
}

IMHash* IMHash::s_create(HASH_TYPE type)
{
	const EVP_MD* md(_getMD(type));
	if ( NULL == md ) return NULL;

	EVP_MD_CTX* ctx(new EVP_MD_CTX);
	if ( ctx == NULL ) return NULL;

	do
	{
		EVP_MD_CTX_init(ctx);
		if ( 0 == EVP_DigestInit_ex(ctx, md, NULL) ) break;

		IMHash* hash(new IMHash);
		if ( NULL == hash ) break;

		hash->m_ctx = ctx;
		return hash;
	} while (false);

	EVP_MD_CTX_cleanup(ctx);
	delete ctx;
	return NULL;
}

void IMHash::s_destroy(IMHash* obj)
{
	delete obj;
}

bool IMHash::s_hash(char* obuf, size_t* olen, const char* buf, size_t blen, HASH_TYPE type)
{
	IMHash* hash(s_create(type));
	if ( NULL == hash ) return false;

	do
	{
		if ( !hash->update(buf, blen) ) break;
		if ( !hash->final(obuf, olen) ) break;
		s_destroy(hash);
		return true;
	} while (false);

	s_destroy(hash);
	return false;
}

bool IMHash::s_getSize(size_t& mdsize, HASH_TYPE type)
{
	const EVP_MD* md = _getMD(type);
	if ( NULL == md ) return false;

	mdsize = EVP_MD_size(md);
	return true;
}

bool IMHash::update(const char* buf, size_t blen)
{
	return 0 != EVP_DigestUpdate(_getContext(m_ctx), buf, blen);
}

bool IMHash::final(char* buf, size_t* _blen)
{
	unsigned int blen(0);
	bool res(0 != EVP_DigestFinal_ex(_getContext(m_ctx), (unsigned char*)buf, &blen));
	if ( res && _blen ) *_blen = blen;
	return res;
}

size_t
IMHash::getSize(void) const
{
	return EVP_MD_CTX_size(_getContext(m_ctx));
}

